/*
 * Copyright (C) 2011 Cyril Mottier (http://www.cyrilmottier.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alstudio.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;

import com.loovee.imaohu.R;

/**
 * <p>
 * Visual indicator of a paged content. The PageIndicator usually displays a
 * line of dots. Each dot represents a page. The PageIndicator supports two
 * types of dots.
 * </p>
 * <ul>
 * <li>{@link DotType#SINGLE}: all dots are drawn but only one is in the
 * selected state at a time. The selected one represents the currently visible
 * page.</li>
 * <li>{@link DotType#MULTIPLE}: the selected page is actually represented by
 * the amount of dots currently being drawn. This behavior is similar to the one
 * visible on the Android Launcher application.</li>
 * </ul>
 * <p>
 * You can have a look at GDCatalog to get a sample code of how to use a
 * PageIndicator in addition to a {@link PagedView}.
 * </p>
 * 
 * @author Cyril Mottier
 */
public class PageIndicator extends View {

	/**
	 * Constant that may be used to select none of the dots in the PageIndicator
	 */
	public static final int NO_ACTIVE_DOT = -1;

	/**
	 * Interface containing of dot types supported by the PageIndicator class.
	 * 
	 * @author Cyril Mottier
	 */
	public interface DotType {

		/**
		 * Represents the single dot type. Only one selected dot may be drawn at
		 * a time.
		 */

		int SINGLE = 0;

		/**
		 * Represents the multiple dot type. Several selected dot may be drawn
		 * at a time. The number of dots drawn represents the currently
		 * remaining page count.
		 */
		int MULTIPLE = 1;
	}

	private static final int MIN_DOT_COUNT = 1;

	private static Rect sInRect = new Rect();
	private static Rect sOutRect = new Rect();

	private int mGravity;
	private int mDotSpacing;
	private Drawable mDotDrawable;
	private int mDotCount;
	private int mDotType;

	private int mActiveDot;

	private int[] mExtraState;

	private boolean mInitializing;

	public PageIndicator(Context context) {
		this(context, null);
	}

	public PageIndicator(Context context, AttributeSet attrs) {
		this(context, attrs, R.attr.gdPageIndicatorStyle);
	}

	public PageIndicator(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initPageIndicator();

		mInitializing = true;

		TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.PageIndicator, defStyle, 0);
		setDotCount(a.getInt(R.styleable.PageIndicator_dotCount, mDotCount));
		setActiveDot(a.getInt(R.styleable.PageIndicator_activeDot, mActiveDot));
		setDotDrawable(a.getDrawable(R.styleable.PageIndicator_dotDrawable));
		setDotSpacing(a.getDimensionPixelSize(
				R.styleable.PageIndicator_dotSpacing, mDotSpacing));
		setGravity(a.getInt(R.styleable.PageIndicator_gravity, mGravity));
		setDotType(a.getInt(R.styleable.PageIndicator_dotType, mDotType));

		a.recycle();

		mInitializing = false;
	}

	private void initPageIndicator() {
		mDotCount = MIN_DOT_COUNT;
		mGravity = Gravity.CENTER;
		mActiveDot = 0;
		mDotSpacing = 0;
		mDotType = DotType.SINGLE;

		mExtraState = onCreateDrawableState(1);
		mergeDrawableStates(mExtraState, SELECTED_STATE_SET);
	}

	/**
	 * Get the maximum number of dots to be drawn.
	 * 
	 * @return The maximum number of dots.
	 * @see #setDotCount(int)
	 */
	public int getDotCount() {
		return mDotCount;
	}

	/**
	 * Set the number of dots.
	 * 
	 * @param dotCount
	 *            The number oF dots
	 * @see #getDotCount()
	 */
	public void setDotCount(int dotCount) {
		if (dotCount < MIN_DOT_COUNT) {
			dotCount = MIN_DOT_COUNT;
		}

		if (mDotCount != dotCount) {
			mDotCount = dotCount;
			requestLayout();
			invalidate();
		}
	}

	/**
	 * Return the current active dot. Depending on the current dot type of this
	 * PageIndicator the current active dot may be the number of displayed dots
	 * or the index of the selected dot
	 * 
	 * @return The current active dot index or dots count
	 * @see #setActiveDot(int)
	 */
	public int getActiveDot() {
		return mActiveDot;
	}

	/**
	 * Set the index of the active dot or the number of active dots. Depending
	 * on the current dot type of this PageIndicator the current active dot may
	 * be the number of displayed dots or the index of the selected dot
	 * 
	 * @param activeDot
	 *            The number/index of (the) active dot(s)
	 * @see #getActiveDot()
	 */
	public void setActiveDot(int activeDot) {
		if (activeDot < 0) {
			activeDot = NO_ACTIVE_DOT;
		}

		switch (mDotType) {
		case DotType.SINGLE:
			if (activeDot > mDotCount - 1) {
				activeDot = NO_ACTIVE_DOT;
			}
			break;

		case DotType.MULTIPLE:
			if (activeDot > mDotCount) {
				activeDot = NO_ACTIVE_DOT;
			}
		}

		mActiveDot = activeDot;
		invalidate();
	}

	/**
	 * Return the Drawable currently used for each dot.
	 * 
	 * @return The Drawable used to draw each dot.
	 */
	public Drawable getDotDrawable() {
		return mDotDrawable;
	}

	/**
	 * Set the Drawable used for each dot. The given Drawable may be a
	 * StateListDrawable in order to take advantage of the selection system. If
	 * your StateListDrawable contains a android.R.attr.state_selected state,
	 * the Drawable will be used to represent a selected dot.
	 * <em><strong>Note :</strong> this methods does not support Drawable
	 * that has no intrinsic dimensions.</em>
	 * 
	 * @param dotDrawable
	 *            The Drawable used to represents a dot
	 */
	public void setDotDrawable(Drawable dotDrawable) {
		//System.out.println("#########" + dotDrawable);
		dotDrawable = getResources().getDrawable(
				R.drawable.gd_page_indicator_dot);
		if (dotDrawable != mDotDrawable) {
			if (mDotDrawable != null) {
				mDotDrawable.setCallback(null);
			}

			mDotDrawable = dotDrawable;

			if (dotDrawable != null) {

				//System.out.println("#########11111111");
				if (dotDrawable.getIntrinsicHeight() == -1
						|| dotDrawable.getIntrinsicWidth() == -1) {
					// Do not accept Drawable with no intrinsic dimensions.
					//System.out.println("#########222222222222");
					return;
				}

				dotDrawable.setBounds(0, 0, dotDrawable.getIntrinsicWidth(),
						dotDrawable.getIntrinsicHeight());
				dotDrawable.setCallback(this);
				if (dotDrawable.isStateful()) {
					dotDrawable.setState(getDrawableState());
				}
			} else {
				//System.out.println("#########333333");
			}

			requestLayout();
			invalidate();
		} else {
			//System.out.println("#########4444");
		}

	}

	/**
	 * The spacing between each dot
	 * 
	 * @return The spacing between each dot
	 */
	public int getDotSpacing() {
		return mDotSpacing;
	}

	/**
	 * Set the spacing between each dot
	 * 
	 * @param dotSpacing
	 *            The spacing between each dot.
	 */
	public void setDotSpacing(int dotSpacing) {
		if (dotSpacing != mDotSpacing) {
			mDotSpacing = dotSpacing;
			requestLayout();
			invalidate();
		}
	}

	/**
	 * Return the gravity used to draw dots/
	 * 
	 * @return The current gravity
	 */
	public int getGravity() {
		return mGravity;
	}

	/**
	 * Specifies how to align the dots by the view's x- and/or y-axis when the
	 * space taken by the dots is smaller than the view.
	 * 
	 * @param gravity
	 *            The gravity
	 */
	public void setGravity(int gravity) {
		if (mGravity != gravity) {
			mGravity = gravity;
			invalidate();
		}
	}

	/**
	 * The current dot type
	 * 
	 * @return The dot type of this {@link PageIndicator}
	 * @see DotType
	 */
	public int getDotType() {
		return mDotType;
	}

	/**
	 * Specifies the type of dot actually drawn by this {@link PageIndicator}
	 * 
	 * @param dotType
	 *            The dot type to use
	 * @see DotType
	 */
	public void setDotType(int dotType) {
		if (dotType == DotType.SINGLE || dotType == DotType.MULTIPLE) {
			if (mDotType != dotType) {
				mDotType = dotType;
				invalidate();
			}
		}
	}

	@Override
	public void requestLayout() {
		if (!mInitializing) {
			super.requestLayout();
		}
	}

	@Override
	public void invalidate() {
		if (!mInitializing) {
			super.invalidate();
		}
	}

	@Override
	protected boolean verifyDrawable(Drawable who) {
		return super.verifyDrawable(who) || who == mDotDrawable;
	}

	@Override
	protected void drawableStateChanged() {
		super.drawableStateChanged();
		mExtraState = onCreateDrawableState(1);
		mergeDrawableStates(mExtraState, SELECTED_STATE_SET);
		invalidate();
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

		Drawable d = mDotDrawable;

		int width = 0;
		int height = 0;
		if (d != null) {
			width = mDotCount * (d.getIntrinsicWidth() + mDotSpacing)
					- mDotSpacing;
			height = d.getIntrinsicHeight();
		}

		width += getPaddingRight() + getPaddingLeft();
		height += getPaddingBottom() + getPaddingTop();

		setMeasuredDimension(resolveSize(width, widthMeasureSpec),
				resolveSize(height, heightMeasureSpec));
	}

	@Override
	protected void onDraw(Canvas canvas) {

		final Drawable d = mDotDrawable;
		if (d != null) {

			final int count = mDotType == DotType.SINGLE ? mDotCount
					: mActiveDot;

			if (count <= 0) {
				return;
			}

			final int h = d.getIntrinsicHeight();
			final int w = Math.max(0, count
					* (d.getIntrinsicWidth() + mDotSpacing) - mDotSpacing);

			final int pRight = getPaddingRight();
			final int pLeft = getPaddingLeft();
			final int pTop = getPaddingTop();
			final int pBottom = getPaddingBottom();

			sInRect.set(pLeft, pTop, getWidth() - pRight, getHeight() - pBottom);
			Gravity.apply(mGravity, w, h, sInRect, sOutRect);

			canvas.save();
			canvas.translate(sOutRect.left, sOutRect.top);
			for (int i = 0; i < count; i++) {
				if (d.isStateful()) {
					int[] state = getDrawableState();
					if (mDotType == DotType.MULTIPLE || i == mActiveDot) {
						state = mExtraState;
					}
					// HACK Cyril: The following code prevent the setState call
					// from invalidating the View again (which will result in
					// calling onDraw over and over again).
					d.setCallback(null);
					d.setState(state);
					d.setCallback(this);
				}
				d.draw(canvas);
				canvas.translate(mDotSpacing + d.getIntrinsicWidth(), 0);
			}
			canvas.restore();
		}
	}

	static class SavedState extends BaseSavedState {

		int activeDot;

		SavedState(Parcelable superState) {
			super(superState);
		}

		private SavedState(Parcel in) {
			super(in);
			activeDot = in.readInt();
		}

		@Override
		public void writeToParcel(Parcel out, int flags) {
			super.writeToParcel(out, flags);
			out.writeInt(activeDot);
		}

		public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
			public SavedState createFromParcel(Parcel in) {
				return new SavedState(in);
			}

			public SavedState[] newArray(int size) {
				return new SavedState[size];
			}
		};
	}

	@Override
	public Parcelable onSaveInstanceState() {
		Parcelable superState = super.onSaveInstanceState();
		SavedState ss = new SavedState(superState);

		ss.activeDot = mActiveDot;

		return ss;
	}

	@Override
	public void onRestoreInstanceState(Parcelable state) {
		SavedState ss = (SavedState) state;
		super.onRestoreInstanceState(ss.getSuperState());

		mActiveDot = ss.activeDot;
	}
}
